<?php
/**
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * @author Eddie Drapkin <eddie@frameweld.com>
 * @license http://www.gnu.org/licenses/lgpl-3.0.txt
 * @package Solphr
 * @subpackage Request
 * @version 0.1-pre
 * 
 */

/**
 * Solphr_Request_Curl class.  Used internally to manage a connection to Solr with cURL.  
 * 
 * Why cURL? Because cURL is easy to use, flexible and not noticeably slower (or faster, for 
 * that matter) than any of the alternatives and while something like fsockopen/fgets would be truly
 * cross-platform (as far as LAMP platforms go, anyway), cURL is a lot more likely to be enabled
 * than something like file_get_contents, which requires url_fopen be true and is the most commonly
 * used method (at least in my experience).
 * 
 * @author Eddie Drapkin <eddie@frameweld.com>
 * @license http://www.gnu.org/licenses/lgpl-3.0.txt
 * @package Solphr
 * @subpackage Request
 */
class Solphr_Request_Curl implements Solphr_Request_Interface {
	/**
	 * The curl handle resource.
	 * @var resource
	 */
	private $curl;
	
	/**
	 * The data that is being sent to Solr.
	 * @var string
	 */
	private $data;
	
	/**
	 * The URL of Solr.
	 * @var string
	 */
	private $URL;
	
	/**
	 * HTTP request method.
	 * @var int
	 */
	private $method;
	
	/**
	 * 
	 * @see Solphr/Request/Solphr_Request.php#queryEscapeWord()
	 */
	static public function queryEscapeWord($query) {
		$replace = explode(' ' , self::SPECIAL_CHARACTERS);
		$with = explode(' ', self::SPECIAL_REPLACEMENTS);
		return str_replace($replace, $with, $query);
	}
	
	static private function escapeWalk(&$val, $key) {
		$val = trim($val);
		if(strpos($val, ' ') !== false) {
			$val = "\"$val\"";
		} else {
			$val = Solr_Request_Curl::queryEscapeWord($val); 
		}
	}
	
	/**
	 * 
	 * @see Solphr/Request/Solphr_Request_Interface.php#queryEscape()
	 */
	static public function queryEscape($query) {
		if(!is_array($query)) {
			$query = preg_split("/[\\s,]*\\\"([^\\\"]+)\\\"[\\s,]*|[\\s,]*'([^']+)'[\\s,]*|[\\s,]+/", $query, 0, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);	
		}
		
		if(array_walk($query, array('Solr_Request_Curl', 'escapeWalk')) === false) {
			return false;
		} else {
			return $query;
		}
	} 
	
	/**
	 * (Probably silly) utility function to easily construct full URLs for the class.
	 * @param string $host Solr host
	 * @param string|int $port Solr port
	 * @param string $path Solr path
	 * @return string The built URL.
	 */
	static public function buildUrl($host, $port, $path) {
		if(substr($path, 0, 1) == '/') {
			$path = substr($path, 1);
		}

		return "http://{$host}:{$port}/{$path}";
	}
	
	/**
	 * Class constructor
	 * @param string $URL The full URL to Solr.
	 * @param int $method One of the Solr_Request GET/POST class constants.
	 * @throws Solphr_Exception
	 */
	public function __construct($URL, $method = self::GET) {
		$this->curl = curl_init($URL);

		if($method !== self::GET && $method !== self::POST) {
			throw new Solphr_Exception('Invalid method, please use one of Solphr_Request::GET or Solphr_Request::POST');
		} elseif($method === self::POST) {
			curl_setopt($this->curl, CURLOPT_POST, true);
		} 
		
		$this->method = $method;
		$this->URL = $URL;
		
		/*
		 * cURL options!
		 */
		$options = array(
			CURLOPT_RETURNTRANSFER => true, //we're going to be fetching XML/JSON, so we'd like a string please.
			CURLOPT_SSL_VERIFYHOST => false, //I *really* hope if you're running Solr on SSL you didn't pay for a certificate
			CURLOPT_SSL_VERIFYPEER => false, //^
			CURLOPT_CRLF => true, //Just in case the line feeds are sent incorrectly, we can be sure they'll be consistent
			CURLOPT_DNS_USE_GLOBAL_CACHE => false, //This isn't thread-safe, which may not be an issue and chances are no DNS will be used anyway, certainly doesn't hurt to disable this, though.
			CURLOPT_FOLLOWLOCATION => false, //There should NEVER be a Location: header sent by Solr, so disabling it could potentially prevent some security issues.  Another case of "doesn't hurt to remove."
			CURLOPT_HEADER => false, //No need for these
			CURLOPT_CONNECTTIMEOUT => 1, //timeout to connect is 1s
			CURLOPT_TIMEOUT => 2 //timeout is 2s
		);
		
		curl_setopt_array($this->curl, $options);
	}
	
	/**
	 * @see Solphr/Request/Solphr_Request_Interface.php#ping()
	 */
	public function ping() {
		$curl = curl_copy_handle($this->curl);
		curl_setopt($curl, CURL_NOBODY, true); //automatically sets request method to HEAD
		$ret = (bool)curl_exec($curl);
		curl_close($curl);
		return $ret;
	}
	
	/**
	 * @see Solphr/Request/Solphr_Request_Interface.php#setURL($URL)
	 */
	public function setURL($URL) {
		curl_setopt($this->curl, CURLOPT_URL, $URL);
	}
	
	/**
	 * @see Solphr/Request/Solphr_Request_Interface.php#setDataFromArray($data)
	 */
	public function setDataFromArray(array $data) {
		$this->data = http_build_query($data);
		$this->data = preg_replace('/%5B([0-9]*?)%5D=/', '=', $this->data); //solr likes a=b&a=c whereas php likes a[1]=b&a[2]=c
	}
	
	/**
	 * @see Solphr/Request/Solphr_Request_Interface.php#setDataFromString($data, $escaped)
	 */
	public function setDataFromString($data, $escaped = false) {
		$this->data = ($escaped) ? $data : urlencode(self::queryEscape($data));
	}
	
	/**
	 * @see Solphr/Request/Solphr_Request_Interface.php#commit()
	 */
	public function commit($xml = true) {
		$curl = curl_copy_handle($this->curl);
		
		if($this->method == self::POST) {
			if($xml === true) {
				curl_setopt($curl, CURLOPT_HTTPHEADER, array("Content-type:text/xml; charset=utf-8"));
			}
			curl_setopt($curl, CURLOPT_POSTFIELDS, $this->data);
		} else {
			curl_setopt($curl, CURLOPT_URL, "{$this->URL}?{$this->data}");
		}
		
		$ret = curl_exec($curl);
		curl_close($curl);
		return $ret;
	}
}